package LLParser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;

import Phase1.MatchObject;
import Phase1.RDPUtility;

public class MiniREParser {
	
	public enum FuncType {NONE, ASSIGNMENT, COUNT, MAXFREQ, REPLACE, REC_REPLACE, PRINT, FIND};
	public enum BinOp {DIFF, UNION, INTERS};
	
	private String fileName;
	private ParseTable pTable;
	private ArrayList<Rule> ruleList;
	private String start;
	private Stack<String> stack;
	private FuncType funcType;
	private Set tokenSet;
	private HashMap valueMap;
	
	public MiniREParser(String fileName, ParseTable pTable, String start, ArrayList<Rule> ruleList, Set tokenSet)
	{
		this.fileName = fileName;
		this.pTable = pTable;
		this.start = start;
		this.ruleList = ruleList;
		this.tokenSet = tokenSet;
		funcType = FuncType.NONE;
		
		valueMap = new HashMap();
		stack = new Stack<String>();
		
		stack.push("$");
		stack.push(start);
	}
	
	public void parse(String line)
	{
		int ruleNum;
		String token = "", preToken = "", postToken = "";
		ArrayList<String> idList = new ArrayList<String>();
		ArrayList<String> regexList = new ArrayList<String>();
		ArrayList<String> asciiList = new ArrayList<String>();
		ArrayList<BinOp> opList = new ArrayList<BinOp>();

		Scanner scan = new Scanner(line);
		
		while(scan.hasNext())
		{
			if(token.equals(""))
				token = scan.next();
			else
				token += scan.next();
			
			//Trim semicolon ';'
			while(token.charAt(token.length() - 1) == ';')
			{
				postToken = token.charAt(token.length() - 1) + postToken;
				token = token.substring(0, token.length() - 1);
			}
			
			//check if incomplete string or regex
			if(!(token.charAt(0) == '\'' && (token.charAt(token.length() - 1) != '\'' ||(token.charAt(token.length() - 1) == '\'' && token.charAt(token.length() - 2) == '\\'))
					|| token.charAt(0) == '"' && (token.charAt(token.length() - 1) != '"' ||(token.charAt(token.length() - 1) == '"' && token.charAt(token.length() - 2) == '\\'))))
			{
				//Trim front tokens
				while(token.charAt(0) == '(' || token.charAt(0) == '#')
				{
					preToken += token.charAt(0);
					token = token.substring(1);
				}
				
				//Trim end tokens
				while(token.charAt(token.length() - 1) == ')' || token.charAt(token.length() - 1) == ';')
				{
					postToken = token.charAt(token.length() - 1) + postToken;
					token = token.substring(0, token.length() - 1);
				}
				
				while(!preToken.equals("") || !token.equals("") || !postToken.equals(""))
				{
					if(preToken.equals("#"))
						funcType = FuncType.COUNT;
					
					//Handle PreTokens
					if(!preToken.equals(""))
					{
						if(stack.peek().charAt(0) == '<')
						{
							ruleNum = pTable.get(stack.peek(), preToken.substring(0, 1));
							stack.pop();
							addToStack(ruleNum);
						}
						else if(stack.peek().equals(preToken.substring(0, 1)))
						{
							stack.pop();
							preToken = preToken.substring(1);
						}
						else
						{
							System.out.println("Shit's Broken");
							System.out.println(line);
							System.exit(1);
						}
					}
					else if(!token.equals(""))
					{
						if(!isToken(token))
						{
							if(isID(token))
							{
								if(valueMap.get(token) == null)
									valueMap.put(token, null);
								idList.add(token);
								token = "ID";
							}
							else if(isASCII(token))
							{
								asciiList.add(token.substring(1, token.length() - 1));
								token = "ASCII-STR";
							}
							else if(isREGEX(token))
							{
								regexList.add(token.substring(1, token.length() - 1));
								token = "REGEX";
							}
						}
						
						if(stack.peek().charAt(0) == '<')
						{
							ruleNum = pTable.get(stack.peek(), token);
							stack.pop();
							addToStack(ruleNum);
						}
						else if(stack.peek().equals(token))
						{
							//Add binOps to list
							switch(token)
							{							
								case "begin":
									funcType = FuncType.NONE;
									break;
								case "end":
									funcType = FuncType.NONE;
									break;
								case "=":
									funcType = FuncType.ASSIGNMENT;
									break;
								case "maxfreqstring":
									funcType = FuncType.MAXFREQ;
									break;
								case "find":
									funcType = FuncType.FIND;
									break;
								case "print":
									funcType = FuncType.PRINT;
									break;
								case "replace":
									funcType = FuncType.REPLACE;
									break;
								case "recursivereplace":
									funcType = FuncType.REC_REPLACE;
									break;
								case "diff":
									opList.add(BinOp.DIFF);
									break;
								case "union":
									opList.add(BinOp.UNION);
									break;
								case "inters":
									opList.add(BinOp.INTERS);
									break;
								default: break;
							}
							
							stack.pop();
							token = "";
						}
						else
						{
							System.out.println("Shit's Broken");
							System.out.println(line);
							System.exit(1);
						}
					}
					else if(!postToken.equals(""))
					{
						if(stack.peek().charAt(0) == '<')
						{
							ruleNum = pTable.get(stack.peek(), postToken.substring(0, 1));
							stack.pop();
							addToStack(ruleNum);
						}
						else if(stack.peek().equals(postToken.substring(0, 1)))
						{
							stack.pop();
							postToken = postToken.substring(1);
						}
						else
						{
							System.out.println("Shit's Broken");
							System.out.println(line);
							System.exit(1);
						}
					}
				}
			}
		}
		
//		if(!stack.isEmpty())
//			System.out.print("\n" + stack.peek() + " " + funcType);
//		else
//			System.out.println();
		
		//Generate function calls with arguments
		//Add return arguments to argsList also add the Type to the list
		switch(funcType)
		{
		case ASSIGNMENT:
			valueMap.put(idList.get(0), valueMap.get(idList.get(1)));
			break;
		case COUNT:
			valueMap.put(idList.get(0), ((ArrayList<MatchObject>) valueMap.get(idList.get(1))).size());
			break;
		case MAXFREQ:
			System.out.print(" " + idList.get(0) + " " + idList.get(1));
			
			ArrayList<MatchObject> tempList = (ArrayList<MatchObject>) valueMap.get(idList.get(1));
			HashMap<String, Integer> freqMap = new HashMap<String, Integer>();
			String max = "";
			int maxInt = -1;
			
			for(int i = 0; i < tempList.size(); i++)
			{
				if(freqMap.get(tempList.get(i).getMatch()) == null)
				{
					freqMap.put(tempList.get(i).getMatch(), 1);
				}
				else
				{
					freqMap.put(tempList.get(i).getMatch(), freqMap.get(tempList.get(i).getMatch()) + 1);
			
				}
			}
			
			Iterator it = freqMap.entrySet().iterator();
			
			while(it.hasNext())
			{
				Map.Entry me = (Map.Entry) it.next();
				
				if((Integer) me.getValue() > maxInt)
					max = (String) me.getKey();
			}
			
			
			valueMap.put(idList.get(0), max);
			break;
		case REPLACE:
			RDPUtility.replace(asciiList.get(1), asciiList.get(2), regexList.get(0), asciiList.get(0));
			break;
		case REC_REPLACE:
			RDPUtility.recursivereplace(asciiList.get(1), asciiList.get(2), regexList.get(0), asciiList.get(0));
			break;
		case PRINT:
			if(valueMap.get(idList.get(0)) instanceof Integer)
				System.out.println(valueMap.get(idList.get(0)));
			else if(valueMap.get(idList.get(0)) instanceof String)
				System.out.println(valueMap.get(idList.get(0)));
			else if(valueMap.get(idList.get(0)) instanceof ArrayList)
			{
				ArrayList<MatchObject> printList;
				
				printList = (ArrayList<MatchObject>) valueMap.get(idList.get(0));
				
				for(int i = 0; i < printList.size(); i++)
				{
					System.out.println(printList.get(i).toString());
				}
			}
			break;
		case FIND:
			ArrayList<ArrayList<MatchObject>> matchList = new ArrayList<ArrayList<MatchObject>>();
			ArrayList<MatchObject> outList = new ArrayList<MatchObject>();
			
			for(int i = 0; i < regexList.size(); i++)
			{
				//System.out.print(" " + regexList.get(i) + " " + asciiList.get(i));
				matchList.add(RDPUtility.find(asciiList.get(i), regexList.get(i)));
			}

			//if opList is not empty convert each arraylist to sets
			if(opList.size() > 0)
			{
				for(int i = 0; i < opList.size(); i++)
				{
					if(opList.get(i) == BinOp.INTERS)
					{
						for(int j = 0; j < matchList.get(i).size(); j++)
						{
							for(int k = 0; k < matchList.get(i + 1).size(); k++)
							{
								if(matchList.get(i).get(j).getMatch().equals(matchList.get(i + 1).get(k).getMatch()))
								{
									outList.add(matchList.get(i).get(j));
								}
							}
						}
					}
					else if(opList.get(i) == BinOp.DIFF)
					{
						outList.addAll(matchList.get(i));

						for(int j = 0; j < matchList.get(i + 1).size(); j++)
						{
							for(int k = 0; k < outList.size(); k++)
							{
								if(matchList.get(i + 1).get(j).getMatch().equals(outList.get(k).getMatch()))
								{
									outList.remove(k);
									break;
								}
							}
						}
					}
					else if(opList.get(i) == BinOp.UNION)
					{
						outList.addAll(matchList.get(i));

						for(int j = 0; j < matchList.get(i + 1).size(); j++)
						{
							outList.add(matchList.get(i + 1).get(j));
						}
					}

                    ArrayList<MatchObject> temp = new ArrayList<MatchObject>();
                    temp.addAll(outList);

                    matchList.remove(i+1);
                    matchList.add(i+1, temp);
                    outList.clear();
				}
			}
			
			valueMap.put(idList.get(0), matchList.get(matchList.size() - 1));
			break;
		default: break;
		}
	}
	
	private void addToStack(int ruleNum)
	{
		Rule rule = ruleList.get(ruleNum);
		
		if(!rule.getRule().get(0).equals("EPSILON"))
			for(int i = rule.getRule().size() - 1; i >= 0; i--)
			{
				stack.push(rule.getRule().get(i));
			}
	}
	
	private boolean isToken(String terminal)
	{
		if(tokenSet.contains(terminal))
			return true;
		
		return false;
	}
	
	private boolean isID(String terminal)
	{
        if(terminal.equals("REGEX"))
            return false;
		if(terminal.length() <= 10 && ((terminal.charAt(0) >= 65 && terminal.charAt(0) <= 90)
				|| (terminal.charAt(0) >= 97 && terminal.charAt(0) <= 122)) )
		{
			for(int i = 1; i < terminal.length(); i++)
			{
				if((terminal.charAt(i) < 65 || terminal.charAt(i) > 90) && (terminal.charAt(i) < 97 || terminal.charAt(i) > 122)
						&& (terminal.charAt(i) < 48 || terminal.charAt(i) > 57) && terminal.charAt(i) != 95)
					return false;
			}
			
			return true;
		}
		
		return false;	
	}
	
	private boolean isASCII(String terminal)
	{
		if(terminal.charAt(0) == '"' && terminal.charAt(terminal.length() - 1) == '"')
			return true;
		
		return false;
	}
	
	private boolean isREGEX(String terminal)
	{
		if(terminal.charAt(0) == '\'' && terminal.charAt(terminal.length() -1) == '\'')
			return true;
		
		return false;
	}
}
